/**
 * @license
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import '../test/common-test-setup-karma';
import {
  createChange,
  createChangeMessageInfo,
  createRevision,
} from '../test/test-data-generators';
import {
  BasePatchSetNum,
  ChangeInfo,
  EditPatchSetNum,
  PatchSetNum,
  ReviewInputTag,
} from '../types/common';
import {
  _testOnly_computeWipForPatchSets,
  computeAllPatchSets,
  findEditParentPatchNum,
  findEditParentRevision,
  getParentIndex,
  getRevisionByPatchNum,
  isMergeParent,
  sortRevisions,
} from './patch-set-util';

suite('gr-patch-set-util tests', () => {
  test('getRevisionByPatchNum', () => {
    const revisions = [createRevision(0), createRevision(1), createRevision(2)];
    assert.deepEqual(
      getRevisionByPatchNum(revisions, 1 as PatchSetNum),
      revisions[1]
    );
    assert.deepEqual(
      getRevisionByPatchNum(revisions, 2 as PatchSetNum),
      revisions[2]
    );
    assert.equal(getRevisionByPatchNum(revisions, 3 as PatchSetNum), undefined);
  });

  test('_computeWipForPatchSets', () => {
    // Compute patch sets for a given timeline on a change. The initial WIP
    // property of the change can be true or false. The map of tags by
    // revision is keyed by patch set number. Each value is a list of change
    // message tags in the order that they occurred in the timeline. These
    // indicate actions that modify the WIP property of the change and/or
    // create new patch sets.
    //
    // Returns the actual results with an assertWip method that can be used
    // to compare against an expected value for a particular patch set.
    const compute = (
      initialWip: boolean,
      tagsByRevision: Map<
        number | 'edit' | 'PARENT',
        (ReviewInputTag | undefined)[]
      >
    ) => {
      const change: ChangeInfo = {
        ...createChange(),
        messages: [],
        work_in_progress: initialWip,
      };
      for (const rev of tagsByRevision.keys()) {
        for (const tag of tagsByRevision.get(rev)!) {
          change.messages!.push({
            ...createChangeMessageInfo(),
            tag,
            _revision_number: rev as PatchSetNum,
          });
        }
      }
      const patchSets = Array.from(tagsByRevision.keys()).map(rev => {
        return {num: rev as PatchSetNum, desc: 'test', sha: `rev${rev}`};
      });
      const patchNums = _testOnly_computeWipForPatchSets(change, patchSets);
      const verifier = {
        assertWip(revision: number, expectedWip: boolean) {
          const patchNum = patchNums.find(
            patchNum => patchNum.num === (revision as PatchSetNum)
          );
          if (!patchNum) {
            assert.fail(`revision ${revision} not found`);
          }
          assert.equal(
            patchNum.wip,
            expectedWip,
            `wip state for ${revision} ` +
              `is ${patchNum.wip}; expected ${expectedWip}`
          );
          return verifier;
        },
      };
      return verifier;
    };

    const upload = 'upload' as ReviewInputTag;

    compute(false, new Map([[1, [upload]]])).assertWip(1, false);
    compute(true, new Map([[1, [upload]]])).assertWip(1, true);

    const setWip = 'autogenerated:gerrit:setWorkInProgress' as ReviewInputTag;
    const uploadInWip = 'autogenerated:gerrit:newWipPatchSet' as ReviewInputTag;
    const clearWip = 'autogenerated:gerrit:setReadyForReview' as ReviewInputTag;

    compute(
      false,
      new Map([
        [1, [upload, setWip]],
        [2, [upload]],
        [3, [upload, clearWip]],
        [4, [upload, setWip]],
      ])
    )
      .assertWip(1, false) // Change was created with PS1 ready for review
      .assertWip(2, true) // PS2 was uploaded during WIP
      .assertWip(3, false) // PS3 was marked ready for review after upload
      .assertWip(4, false); // PS4 was uploaded ready for review

    compute(
      false,
      new Map([
        [1, [uploadInWip, undefined, 'addReviewer' as ReviewInputTag]],
        [2, [upload]],
        [3, [upload, clearWip, setWip]],
        [4, [upload]],
        [5, [upload, clearWip]],
        [6, [uploadInWip]],
      ])
    )
      .assertWip(1, true) // Change was created in WIP
      .assertWip(2, true) // PS2 was uploaded during WIP
      .assertWip(3, false) // PS3 was marked ready for review
      .assertWip(4, true) // PS4 was uploaded during WIP
      .assertWip(5, false) // PS5 was marked ready for review
      .assertWip(6, true); // PS6 was uploaded with WIP option
  });

  test('isMergeParent', () => {
    assert.isFalse(isMergeParent(1 as PatchSetNum));
    assert.isFalse(isMergeParent(4321 as PatchSetNum));
    assert.isFalse(isMergeParent('edit' as PatchSetNum));
    assert.isFalse(isMergeParent('PARENT' as PatchSetNum));
    assert.isFalse(isMergeParent(0 as PatchSetNum));

    assert.isTrue(isMergeParent(-23 as PatchSetNum));
    assert.isTrue(isMergeParent(-1 as PatchSetNum));
  });

  test('findEditParentRevision', () => {
    const revisions = [createRevision(0), createRevision(1), createRevision(2)];
    assert.strictEqual(findEditParentRevision(revisions), null);

    revisions.push({
      ...createRevision(),
      _number: EditPatchSetNum,
      basePatchNum: 3 as BasePatchSetNum,
    });
    assert.strictEqual(findEditParentRevision(revisions), null);

    revisions.push(createRevision(3));
    assert.deepEqual(findEditParentRevision(revisions), createRevision(3));
  });

  test('findEditParentPatchNum', () => {
    const revisions = [createRevision(0), createRevision(1), createRevision(2)];
    assert.equal(findEditParentPatchNum(revisions), -1);

    revisions.push(
      {
        ...createRevision(),
        _number: EditPatchSetNum,
        basePatchNum: 3 as BasePatchSetNum,
      },
      createRevision(3)
    );
    assert.deepEqual(findEditParentPatchNum(revisions), 3);
  });

  test('sortRevisions', () => {
    const revisions = [createRevision(0), createRevision(2), createRevision(1)];
    const sorted = [createRevision(2), createRevision(1), createRevision(0)];

    assert.deepEqual(sortRevisions(revisions), sorted);

    // Edit patchset should follow directly after its basePatchNum.
    revisions.push({
      ...createRevision(),
      _number: EditPatchSetNum,
      basePatchNum: 2 as BasePatchSetNum,
    });
    sorted.unshift({
      ...createRevision(),
      _number: EditPatchSetNum,
      basePatchNum: 2 as BasePatchSetNum,
    });
    assert.deepEqual(sortRevisions(revisions), sorted);

    revisions[0].basePatchNum = 0 as BasePatchSetNum;
    const edit = sorted.shift()!;
    edit.basePatchNum = 0 as BasePatchSetNum;
    // Edit patchset should be at index 2.
    sorted.splice(2, 0, edit);
    assert.deepEqual(sortRevisions(revisions), sorted);
  });

  test('getParentIndex', () => {
    assert.equal(getParentIndex(-4 as PatchSetNum), 4);
  });

  test('computeAllPatchSets', () => {
    const expected = [
      {num: 4 as PatchSetNum, desc: 'test', sha: 'rev4'},
      {num: 3 as PatchSetNum, desc: 'test', sha: 'rev3'},
      {num: 2 as PatchSetNum, desc: 'test', sha: 'rev2'},
      {num: 1 as PatchSetNum, desc: 'test', sha: 'rev1'},
    ];
    const patchNums = computeAllPatchSets({
      ...createChange(),
      revisions: {
        rev1: {...createRevision(1), description: 'test'},
        rev2: {...createRevision(2), description: 'test'},
        rev3: {...createRevision(3), description: 'test'},
        rev4: {...createRevision(4), description: 'test'},
      },
    });
    assert.equal(patchNums.length, expected.length);
    for (let i = 0; i < expected.length; i++) {
      assert.deepEqual(patchNums[i], expected[i]);
    }
  });
});
